package JET.physics.bodies;

import java.util.ArrayList;
import JET.geom.Vec2d;
import JET.graphics.elements.Face;
import JET.physics.Border;
import JET.physics.BoundingArea;
import JET.physics.BoundingBox;
import JET.physics.Mass;
import JET.physics.PhysicsHelper;
import JET.physics.RSpring;
import JET.physics.Spring;
import JET.physics.Wall;
import JET.physics.interfaces.SceneTreeNode;
import java.util.Collection;
import java.util.Collections;

/**
 *
 * @author 057Ry
 */
public final class SpringBody extends Body implements SceneTreeNode
{
    static int sbCount;
    int id;
    public String name;

    private ArrayList<Mass> massList;
    private Collection<Mass> massSyncCol;
    private boolean massesChanged = true;

    private ArrayList<Spring> springList;
    private Collection<Spring> springSyncCol;
    private boolean springsChanged = true;

    private ArrayList<RSpring> rSpringList;
    private Collection<RSpring> rSpringSyncCol;
    private boolean rSpringsChanged = true;

    private Border border;
    private BoundingArea boundingArea;
    private int material;

    // TODO: remove this face list creation and place it in constructor ...
    private ArrayList<Face> faceList = new ArrayList<Face>();
    private Collection<Face> faceSyncCol;
    private boolean facesChanged = true;

    // Shape matchig stuff
    public RigidBody rb;
    public Spring[] smSprings;
    public int[]   smMassIndices;
    private boolean matchShape = false;
    
    public SpringBody() {
        massList = new ArrayList<Mass>();
        springList = new ArrayList<Spring>();
        rSpringList = new ArrayList<RSpring>();
        faceList = new ArrayList<Face>();
        border = new Border();
        boundingArea = new BoundingBox();
        material = 0;
        id = sbCount++;
        name = "Spring Body "+id;
        ownerType = OwnerType.Engine;
    }

    /**
     * Builds rigid body used for shape matching functionality and
     * creates springs connecting each mass of mList with corresponding
     * rigid body point.
     * 
     * @param mList List of masses used to set shape of shape matching rigid body.
     *              (coordinates are in engine system, and need not to be centered to origin).
     * 
     */
    public void buildShapeMatchingStruct(ArrayList<Mass> mList) {
        //smBaseVecTab = new Vec2d[mList.size()];
        smMassIndices = new int[mList.size()];
        Vec2d mCenter = PhysicsHelper.getMassCenterOfMasses(mList);
        for(int i=0; i<mList.size(); i++) {
            Vec2d v = new Vec2d(mList.get(i).getPosition());
            v.sub(mCenter);
            //smBaseVecTab[i] = v;
            smMassIndices[i] = getMassList().indexOf(mList.get(i));
        }

        rb = new RigidBody();
        rb.buildBaseShape(mList);
        smSprings = new Spring[mList.size()];
        for(int i=0; i<smSprings.length; i++) {
            Mass m1 = mList.get(i);
            Mass m2 = rb.dynVecList.get(i);
            m2.setMass(m2.getMass()*2);
            smSprings[i] = new Spring(m1, m2, Spring.Type.SpringBody);
            smSprings[i].setKs(15.);
            smSprings[i].setKd(0.0);
            smSprings[i].setTearable(false);
        }
    }

    /**
     *  @return List of masses involved in shape matching
     *          (if no mass is included in shape matching, list is returned empty).
     */
    public ArrayList<Mass> getShapeMatchingMasses() {
        ArrayList<Mass> list = new ArrayList<Mass>();

        if (smMassIndices!=null)
            for(int i=0; i<smMassIndices.length; i++)
                list.add(massList.get(smMassIndices[i]));

        return list;
    }

    public Spring[] getSMSprings() {
        return smSprings;
    }

    synchronized public void zeroForces() {
        for(Mass m : massList)
            m.getForce().zero();
    }

    synchronized public void accumulateExternalForces(Vec2d a) {
        Vec2d gForce = new Vec2d();
        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            for(Mass m : massCol)
            {
                gForce.become(a);
                gForce.scale(m.getMass());
                m.getForce().add(gForce);
            }
        }
    }
    synchronized public void accumulateInternatForces() {
        Collection<Spring> springCol = getSpringListSync();
        synchronized(springCol) {
            for(Spring s : springCol)
                s.doYourJob();
        }

        Collection<RSpring> rSpringCol = getRSpringListSync();
        synchronized(rSpringCol) {
            for(RSpring rs : rSpringCol)
                rs.doYourJob();
        }

        if (matchShape) {
            rb.setPositionAndAngle(getDerivedPosition(), getDerivedAngle());
            for(Spring s : smSprings)
                s.doYourJob();
        }
    }

    public void dampVelocities(double value) {
        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            for(Mass m : massCol)
                m.getVelocity().scale(value);
        }
    }
    
    public void simulate(double dt) {
        getBorder().calculateOrientation();
        getBoundingArea().reset();

        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            for(Mass mass : massCol) {
                mass.simulate(dt);
                getBoundingArea().extendToContain(mass.getPosition());
            }
        }
    }
    
    public boolean isMatchShape() { return matchShape; }
    public void setMatchShape(boolean v) { matchShape = v; }

    public static void zeroSBCount() { sbCount = 0; }

    public int getMaterial() { return material; }
    public void setMaterial(int m) { material = m; }
    
    public Border getBorder() { return border; }
    public BoundingArea getBoundingArea() { return boundingArea; }
    public void updateBoundingArea() {
        if (boundingArea!=null) {
            boundingArea.reset();
            for (Mass m : massList)
            boundingArea.extendToContain(m.getPosition());
        }
    }

    public void addMass(Mass mass) {
        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            massCol.add(mass);
        }
        massesChanged = true;
    }
    //public void addMasses(ArrayList<Mass> massListToAdd) {
    public void addMasses(Collection<Mass> massListToAdd) {
        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            massCol.addAll(massListToAdd);
        }
        massesChanged = true;
    }
    public void removeMass(Mass mass) {
        
        Collection<Mass> massCol = getMassListSync();
        synchronized(massCol) {
            massCol.remove(mass);
        }
        massesChanged = true;
    }

    public Collection<Mass> getMassListSync() {
        if (massesChanged || massSyncCol==null) {
            massSyncCol = Collections.synchronizedList(massList);
            massesChanged = false;
        }

        return massSyncCol;
    }

    /*
     * Springs -----------------------------------------------------------------
     */
    public void addSpring(Spring spring) {
        springList.add(spring);
        springsChanged = true;
    }
    public void removeSpring(Spring spring) {
        Collection<Spring> springCol = getSpringListSync();
        synchronized(springCol) {
            springCol.remove(spring);
        }
        springsChanged = true;
    }
    //public void addSprings(ArrayList<Spring> springListToAdd) {
    public void addSprings(Collection<Spring> springListToAdd) {
        springList.addAll(springListToAdd);
        springsChanged = true;
    }

    public Collection<Spring> getSpringListSync() {
        if (springsChanged || springSyncCol==null) {
            springSyncCol = Collections.synchronizedList(springList);
            springsChanged = true;
        }

        return springSyncCol;
    }

    /*
     * Rotary Springs ----------------------------------------------------------
     */
    public void addRSpring(RSpring rSpring) {
        rSpringList.add(rSpring);
        rSpringsChanged = true;
    }
    public void removeRSpring(RSpring rSpring) {
        Collection<RSpring> rSpringCol = getRSpringListSync();
        synchronized(rSpringCol) {
            rSpringCol.remove(rSpring);
        }
        rSpringsChanged = true;
    }

    public void addRSprings(ArrayList<RSpring> rSpringListToAdd) {
        rSpringList.addAll(rSpringListToAdd);
        rSpringsChanged = true;
    }

    public Collection<RSpring> getRSpringListSync() {
        if (rSpringsChanged || rSpringSyncCol==null) {
            rSpringSyncCol = Collections.synchronizedList(rSpringList);
            rSpringsChanged = false;
        }

        return rSpringSyncCol;
    }

    /*
     * Faces -----------------------------------------------------------------
     */

    public void addFace(Face f) {
        Collection<Face> faceCol = getFaceListSync();
        synchronized(faceCol) {
            faceCol.add(f);
        }
        facesChanged = true;
    }

    public void removeFace(Face f) {
        Collection<Face> faceCol = getFaceListSync();
        synchronized(faceCol) {
            faceCol.remove(f);
        }
        facesChanged = true;
    }

    public Collection<Face> getFaceListSync() {
        if (facesChanged || faceSyncCol==null) {
            faceSyncCol = Collections.synchronizedList(faceList);
            facesChanged = false;
        }

        return faceSyncCol;
    }

    /*
     * Walls -----------------------------------------------------------------
     */
    public synchronized void addWallToBorder(Wall wall) {
        border.addWall(wall);

        // sprawdzamy czy brzeg prawidlowy, jesli tak sortujemy liste mas
        if ( border.isProperlyBuilt() ) {
            ArrayList<Mass> tmpList = new ArrayList<Mass>(massList.size());
            tmpList.addAll( border.getSortedMassList() );
            for(Mass mass : massList)
                if( !tmpList.contains(mass) ) tmpList.add(mass);
            massList = tmpList;
        }
    }
    //public synchronized void addWallsToBorder(ArrayList<Wall> wallListToAdd)
    public synchronized void addWallsToBorder(Collection<Wall> wallListToAdd)
    {
        border.addWalls(wallListToAdd);
        if ( border.isProperlyBuilt() ) {
            ArrayList<Mass> tmpList = new ArrayList<Mass>(massList.size());
            tmpList.addAll( border.getSortedMassList() );
            for(Mass mass : massList)
                if( !tmpList.contains(mass) ) tmpList.add(mass);
            massList = tmpList;
        }
    }
    public synchronized void removeWallFromBorder(Wall wall)
    {
        border.removeWall(wall);
        if ( border.isProperlyBuilt() ) {
            ArrayList<Mass> tmpList = new ArrayList<Mass>(massList.size());
            tmpList.addAll( border.getSortedMassList() );
            for(Mass mass : massList)
                if( !tmpList.contains(mass) ) tmpList.add(mass);
            massList = tmpList;
        }
    }
    public synchronized void removeWallsThatUsesMass(Mass mass)
    {
        border.removeWalls2dThatUsesMass2d(mass);
        if ( border.isProperlyBuilt() ) {
            ArrayList<Mass> tmpList = new ArrayList<Mass>(massList.size());
            tmpList.addAll( border.getSortedMassList() );
            for(Mass m : massList)
                if( !tmpList.contains(m) ) tmpList.add(m);
            massList = tmpList;
        }
    }
    public synchronized void removeSpringsThatUsesMass2d(Mass mass2d)
    {
        for(int i =0; i<springList.size(); i++)
            if( springList.get(i).getM1().equals(mass2d) || springList.get(i).getM2().equals(mass2d) )
                //StaticActions.removeSpringFromSpringBody( springList.get(i--), false);
                removeSpring(springList.get(i));
    }
    public synchronized void removeRSpringsThatUsesMass2d(Mass mass)
    {
        for(int i =0; i<rSpringList.size(); i++)
            if( rSpringList.get(i).getM1().equals(mass) ||
                rSpringList.get(i).getM2().equals(mass) ||
                rSpringList.get(i).getM3().equals(mass))
            { }
    }

    public ArrayList<Mass> getMassList() { return massList; }
    public ArrayList<Spring> getSpringList() { return springList; }
    public ArrayList<RSpring> getRSpringList() { return rSpringList; }
    
    public boolean isOwnerOf(Mass mass) {
        if ( massList.contains(mass) )
            return true;
        return false;
    }
    public boolean isOwnerOf(Wall wall) {
        if ( border.getWallList().contains(wall) )
            return true;
        return false;
    }
    public boolean isOwnerOf(Spring spring) {
        if ( springList.contains(spring) )
            return true;
        return false;
    }
    public boolean isOwnerOf(RSpring rSpring) {
        if ( rSpringList.contains(rSpring) )
            return true;
        return false;
    }

    public synchronized void moveBy(Vec2d vec) {
        for(Mass mass : massList)
            mass.moveBy(vec);
    }

    public Vec2d getCenterVec() {
        
        Vec2d center = new Vec2d();
        Collection<Mass> massCol = getMassListSync();

        synchronized(massCol) {
            for(Mass mass : massCol)
                center.add(mass.getPosition());
            center.scale(1./massCol.size());
        }

        return center;
    }
    
    @Override
    public String toString() {
        return name;
    }

    public int getID() { return id; }
    public String getName() { return name; }
    public void setName(String n) { name = n; }

    public double getMass() {
        double sum = 0.;
        for(Mass m : massList)
            sum += m.getMass();
        
        return sum;
    }
    
    public double getDerivedAngle() {
        int count=0;
        double angle=0.;
        int originalSign = 1;
        double originalAngle = 0;
        Vec2d mCenter = PhysicsHelper.getMassCenterOfMasses(getShapeMatchingMasses());
        for (int i = 0; i<smMassIndices.length; i++) {
            if (massList.get(smMassIndices[i]).getPosition().getSubbed(mCenter).getMagnitude()>=1. &&
                    rb.baseVecList.get(i).getX()!=0 && rb.baseVecList.get(i).getY()!=0) {
                count++;
                Vec2d baseNorm = new Vec2d(rb.baseVecList.get(i));
                baseNorm.normalize();

                Vec2d curNorm = new Vec2d(massList.get(smMassIndices[i]).getPosition());
                curNorm.sub(mCenter);
                curNorm.normalize();

                double dot = baseNorm.dot(curNorm);
                if (dot > 1.0f) { dot = 1.0f; }
                if (dot < -1.0f) { dot = -1.0f; }

                double thisAngle = Math.acos(dot);
                if (!Vec2d.isCCW(baseNorm, curNorm)) { thisAngle = -thisAngle; }

                if (i == 0) {
                        originalSign = (thisAngle >= 0.0f) ? 1 : -1;
                        originalAngle = thisAngle;
                }
                else {
                    double diff = (thisAngle - originalAngle);
                    int thisSign = (thisAngle >= 0.0f) ? 1 : -1;

                    if ((Math.abs(diff) > Math.PI) && (thisSign != originalSign))
                        thisAngle = (thisSign == -1) ? (Math.PI + (Math.PI + thisAngle)) : ((Math.PI - thisAngle) - Math.PI);
                }
                angle += thisAngle;
            }
        }
        //angle /= smMassIndices.length;
        angle /= count;
        return angle;
    }

    public Vec2d getDerivedPosition() {
        //return PhysicsHelper.getMassCenterOfMasses(massList);
        return PhysicsHelper.getMassCenterOfMasses(getShapeMatchingMasses());
    }


    ////////////////////////////////////////////////////////////////////////////
    //                S C E N E   T R E E   I N T E R F A C E                 //
    ////////////////////////////////////////////////////////////////////////////

    public NameTreeNode nameTreeNode = new NameTreeNode();
    public MassesTreeNode massesTreeNode = new MassesTreeNode();
    public SpringsTreeNode springsTreeNode = new SpringsTreeNode();
    public RSpringsTreeNode rSpringsTreeNode = new RSpringsTreeNode();
    public WallsTreeNode wallsTreeNode = new WallsTreeNode();
    public SMSpringsTreeNode smSpringsTreeNode = new SMSpringsTreeNode();

    public SceneTreeNode getChild(int index) {
        switch (index) {
            case 0 : return nameTreeNode;
            case 1 : return massesTreeNode;
            case 2 : return springsTreeNode;
            case 3 : return rSpringsTreeNode;
            case 4 : return wallsTreeNode;
            case 5 : return smSpringsTreeNode;
        }

        return null;
    }

    public int getChildCount() {
        return 6;
    }

    public boolean isLeaf() {
        return false;
    }

    public int getIndexOfChild(Object child) {
        if      (child instanceof NameTreeNode) return 0;
        else if (child instanceof MassesTreeNode) return 1;
        else if (child instanceof SpringsTreeNode) return 2;
        else if (child instanceof RSpringsTreeNode) return 3;
        else if (child instanceof WallsTreeNode) return 4;
        else if (child instanceof SMSpringsTreeNode) return 5;

        else
            return -1;
    }

    public void resetNodeValue(Object o) {
        name = o.toString();
    }

    public class NameTreeNode implements SceneTreeNode {
        public SceneTreeNode getChild(int index) {return null;}
        public int getChildCount() {return 0;}
        public boolean isLeaf() {return true;}
        public int getIndexOfChild(Object child) {return -1;}

        public void resetNodeValue(Object o) {
            name = o.toString();
        }

        public String toString() {
            return "name: '"+name+"'";
        }
    }

    public class MassesTreeNode implements SceneTreeNode {

        public SceneTreeNode getChild(int index) {
            return (SceneTreeNode) massList.get(index);
        }

        public int getChildCount() {
            return massList.size();
        }

        public boolean isLeaf() {
            return massList.size() == 0;
        }

        public int getIndexOfChild(Object child) {
            return massList.indexOf(child);
        }
        
        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Masses";
        }
    }

    public class SpringsTreeNode implements SceneTreeNode {

        public SceneTreeNode getChild(int index) {
            return (SceneTreeNode) springList.get(index);
        }

        public int getChildCount() {
            return springList.size();
        }

        public boolean isLeaf() {
            return springList.size() == 0;
        }

        public int getIndexOfChild(Object child) {
            return springList.indexOf(child);
        }

        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Springs";
        }
    }

    public class RSpringsTreeNode implements SceneTreeNode {

        public SceneTreeNode getChild(int index) {
            return (SceneTreeNode) rSpringList.get(index);
        }

        public int getChildCount() {
            return rSpringList.size();
        }

        public boolean isLeaf() {
            return rSpringList.size() == 0;
        }

        public int getIndexOfChild(Object child) {
            return rSpringList.indexOf(child);
        }

        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Rotary springs";
        }
    }

    public class SMSpringsTreeNode implements SceneTreeNode {

        public SceneTreeNode getChild(int index) {
            if (smSprings!=null)
                return (SceneTreeNode) smSprings[index];
            else
                return null;
        }

        public int getChildCount() {
            if (smSprings!=null)
                return smSprings.length;
            else
                return 0;
        }

        public boolean isLeaf() {
            if (smSprings!=null)
                return false;
            else
                return true;
        }

        public int getIndexOfChild(Object child) {
            if (smSprings!=null)
                for (int i=0; i<smSprings.length; i++)
                    if (smSprings[i].equals(child))
                        return i;

            return -1;
        }

        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Shape-Match Springs";
        }
    }

    public class WallsTreeNode implements SceneTreeNode {

        public SceneTreeNode getChild(int index) {
            return (SceneTreeNode) getBorder().getWallList().get(index);
        }

        public int getChildCount() {
            return getBorder().getWallsCount();
        }

        public boolean isLeaf() {
            return getBorder().getWallsCount() == 0;
        }

        public int getIndexOfChild(Object child) {
            return getBorder().getWallList().indexOf(child);
        }

        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Walls";
        }
    }
}

